<?php
/**
 * @package    system
 * @subpackage service
 */
class ServiceInvoker
{
    /**
     * The level of backtrace to retrieve the call
     *
     * @var integer
     */
    protected static $backtraceLevel = 1;

    /**
     * Call registry
     *
     * @var array
     */
    protected static $callStack    = array();

    /**
     * Enable output cache for class/method with same arguments
     *
     * @var bool
     */
    public static $enableOutputCache = false;

    /**
     * Output buffer. Store the output for last call
     *
     * @var mixed
     */
    protected static $output; // Nao pode ter valor algum

    /**
     * Counter for each class/method recursevely call
     *
     * @var array
     */
    protected static $counter = array();

    /**
     * Call limit for each class/method service could be execute
     *
     * @var integer
     */
    public static $maxRecursion = 10;


    /**
     * Used for invoke a class/method internally before your real implementation
     *
     * @uses
     * <code>
     * class ExampleService
     * {
     *     function exampleServiceMethod($foo,$bar)
     *     {
     *         if (ServiceInvoker::invoke()) return ServiceInvoker::output();
     *         // method implementation
     *     }
     * }
     * </code>
     *
     * @param  integer $backtraceLevel
     * @return bool
     */
    public static function invoke($backtraceLevel = null)
    {
        $backtrace = debug_backtrace();

        if (!is_integer($backtraceLevel)) {
            $backtraceLevel = self::$backtraceLevel;
        }
        $call = $backtrace[$backtraceLevel];

        $callId    = self::generateCallId($call);
        $callStack = (array) self::$callStack;
        $lastCall  = null;
        $lastIdx   = count($callStack) - 1;

        if (isset($callStack[$lastIdx])) {
            $lastCall = $callStack[$lastIdx];
        }
        $invoke = self::checkInvoke($lastCall,$callId,$call);

        if ($invoke !== true) return false;

        $call['invoked'] = true;
        self::$callStack[]    = &$call;

        ServicesSchemas::$useFileCache = true;
        $logger = ServiceLogger::getInstance();

        try {
            $callData = self::parseCallData($call);
            ServiceValidator::validateInput($callData, $call['class'],$call['function']);
            // Call service
            $startTime = microtime(true);
            $hasCache=false;

            if (self::$enableOutputCache == true) {
                foreach (self::$callStack as $idxCall) {
                    $idxCallId = self::generateCallId($idxCall);
                    $idxCallData = self::parseCallData($idxCall);

                    if ($idxCallId == $callId && $idxCallData == $callData && isset($idxCall['output'])) {
                        $call['output'] = $idxCall['output'];
                        $hasCache=true;
                        break;
                    }
                }
            }
            if ($hasCache == false) {
                $call['output']= call_user_func_array(array($call['class'], $call['function']), $call['args']);
            }
            $endTime  = microtime(true);
            $duration = $endTime - $startTime;
            $call['duration'] = $duration;
            $logger->logSuccess($call['class'], $call['function'], $duration);

        } catch (Exception $e) {
            $logger->logFailure($call['class'], $call['function'],$e);

            if($e instanceof Doctrine_Connection_Exception) {
                throw new ServiceDataException($e->getMessage());
            }
            throw $e;
        }
        // Validating output
        ServiceValidator::validateOutput($call['output'], $call['class'], $call['function'],$callData);
        self::$output = $call['output'];
        return true;
    }

    /**
     * Check if the service will invoked
     *
     * @param null|array $lastCall
     * @param string     $callId
     * @param array      $call
     *
     * @return bool
     */
    protected static function checkInvoke($lastCall,$callId,$call)
    {
        $invoke         = false;
        $checkRecursion = false;

        if ($lastCall === null) {
            $invoke = true;
        } else {
            $lastCallId = self::generateCallId($lastCall);

            if ($lastCallId == $callId) {
                $checkRecursion = true;

                if ($lastCall['args'] != $call['args']) {
                    $invoke = true;
                } else if (isset($call['line']) && $call['line'] != '') {
                    if ($lastCall['line'] != $call['line']) {
                        $invoke = true;
                    }
                } else {
                    $checkRecursion = false;
                }
            } else {
                $invoke = true;
            }
        }
        if ($checkRecursion == true) {
            if (self::$counter[$callId] > self::$maxRecursion) {
                throw new ServiceException('Max recursions exceeds: ' . self::$counter[$callId] . 'in ' . $call['class'].'::'.$call['function']);
            } else {
                self::$counter[$callId] += 1;
            }
        } else {
            self::$counter[$callId] = 0;
        }
        return $invoke;
    }

    /**
     * Return the last output generated by last call
     *
     * @return mixed
     */
    public static function output()
    {
        return self::$output;
    }


    /**
     * Generate a string with call id
     *
     * @param  array $call
     * @return string
     */
    public static function generateCallId($call)
    {
        return $call['class'] . '.' . $call['function'];
    }

    /**
     * Parse call data from call arguments
     *
     * @param  array $callArgs
     * @return object
     */
    public static function parseCallData($call)
    {
        return array_shift($call['args']);
    }
}

?>